/*
 * graph2D
 * Copyright (c) 2009 Shun Moriya <shun@mnu.sakura.ne.jp>
 *
 * The MIT License
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
 * THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#define INCLUDE_FROM_OBJC_C
#import "data_impl.h"
#import "soundPlayer_impl.h"
#import <OpenAL/al.h>
#import <OpenAL/alc.h>
#import <AudioToolbox/AudioToolbox.h>
#import <AudioToolbox/ExtendedAudioFile.h>

namespace Graph2D
{
	namespace Implementation
	{
		namespace Sound
		{
			bool load(const size_t bufferName, const std::string& filename)
			{
				UInt32 size;
				SInt64 fileLengthFrames;
				void* data = NULL;
				bool result = false;

				// リソースのURLを作成
				CFURLRef fileURL = (CFURLRef)[DataController getResouceUriWithString:[NSString stringWithUTF8String:filename.c_str()]];

				// オーディオファイルを開く
				ExtAudioFileRef audioFile;
				if(ExtAudioFileOpenURL(fileURL, &audioFile))
					goto ABORT;

				// オーディオデータフォーマットを取得する
				AudioStreamBasicDescription inputFormat;
				size = sizeof(inputFormat);
				if(ExtAudioFileGetProperty(audioFile, kExtAudioFileProperty_FileDataFormat, &size, &inputFormat))
					goto ABORT;

				// アウトプットフォーマットを設定する
				AudioStreamBasicDescription outputFormat;
				outputFormat.mSampleRate = inputFormat.mSampleRate;
				outputFormat.mChannelsPerFrame = inputFormat.mChannelsPerFrame;
				outputFormat.mFormatID = kAudioFormatLinearPCM;
				outputFormat.mBytesPerPacket = 2 * outputFormat.mChannelsPerFrame;
				outputFormat.mFramesPerPacket = 1;
				outputFormat.mBytesPerFrame = 2 * outputFormat.mChannelsPerFrame;
				outputFormat.mBitsPerChannel = 16;
				outputFormat.mFormatFlags = kAudioFormatFlagsNativeEndian|kAudioFormatFlagIsPacked|kAudioFormatFlagIsSignedInteger;
				if(ExtAudioFileSetProperty(audioFile, kExtAudioFileProperty_ClientDataFormat, sizeof(outputFormat), &outputFormat))
					goto ABORT;

				// フレーム数を取得する
				fileLengthFrames = 0;
				size = sizeof(fileLengthFrames);
				if(ExtAudioFileGetProperty(audioFile, kExtAudioFileProperty_FileLengthFrames, &size, &fileLengthFrames))
					goto ABORT;

				// バッファを用意する
				UInt32 bufferSize;
				AudioBufferList dataBuffer;
				bufferSize = fileLengthFrames * outputFormat.mBytesPerFrame;
				data = malloc(bufferSize);
				dataBuffer.mNumberBuffers = 1;
				dataBuffer.mBuffers[0].mDataByteSize = bufferSize;
				dataBuffer.mBuffers[0].mNumberChannels = outputFormat.mChannelsPerFrame;
				dataBuffer.mBuffers[0].mData = data;

				// バッファにデータを読み込む
				if(ExtAudioFileRead(audioFile, (UInt32*)&fileLengthFrames, &dataBuffer))
					goto ABORT;

				// 出力値をバッファに設定する
				alBufferData(
					bufferName,
					(outputFormat.mChannelsPerFrame > 1) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16,
					data,
					static_cast<ALsizei>(bufferSize),
					static_cast<ALsizei>(outputFormat.mSampleRate)
				);
				if(alGetError() == AL_NO_ERROR)
					result = true;

ABORT:
				// バッファを解放
				free(data);

				// オーディオファイルを破棄する
				if(audioFile)
					ExtAudioFileDispose(audioFile);

				return result;
			}
#if 0
			////////////////////////////////////////////////////////////////////////////////
			// async
			////////////////////////////////////////////////////////////////////////////////
			typedef struct Environment
			{
				AudioStreamBasicDescription format;
				ExtAudioFileRef file;
			}Environment;

			void* open(const std::string& filename)
			{
				Environment* self = reinterpret_cast<Environment*>(malloc(sizeof(Environment)));
				if(self)
				{
					NSString* name = [NSString stringWithUTF8String:filename.c_str()];

					// リソースのURLを作成
					CFURLRef url = (CFURLRef)[DataController getResouceUriWithString:name];

					// オーディオファイルを開く
					if(ExtAudioFileOpenURL(url, &self->file))
						goto ABORT;

					// オーディオデータフォーマットを取得する
					AudioStreamBasicDescription inputFormat;
					UInt32 size = sizeof(inputFormat);
					if(ExtAudioFileGetProperty(self->file, kExtAudioFileProperty_FileDataFormat, &size, &inputFormat))
						goto ABORT;

					// アウトプットフォーマットを設定する
					self->format.mSampleRate = inputFormat.mSampleRate;
					self->format.mChannelsPerFrame = inputFormat.mChannelsPerFrame;
					self->format.mFormatID = kAudioFormatLinearPCM;
					self->format.mBytesPerPacket = 2 * self->format.mChannelsPerFrame;
					self->format.mFramesPerPacket = 1;
					self->format.mBytesPerFrame = 2 * self->format.mChannelsPerFrame;
					self->format.mBitsPerChannel = 16;
					self->format.mFormatFlags = kAudioFormatFlagsNativeEndian|kAudioFormatFlagIsPacked|kAudioFormatFlagIsSignedInteger;
					if(ExtAudioFileSetProperty(self->file, kExtAudioFileProperty_ClientDataFormat, sizeof(self->format), &self->format))
						goto ABORT;
				}
				return self;

ABORT:
				close(self);
				return NULL;
			}

			unsigned int channels(void* self)
			{
				Environment* environment = reinterpret_cast<Environment*>(self);
				return environment ? environment->format.mChannelsPerFrame : 0;
			}

			bool read(void* self, void* buffer, size_t& size)
			{
				Environment* environment = reinterpret_cast<Environment*>(self);
				if(environment)
				{
					// バッファを用意する
					AudioBufferList dataBuffer;

					dataBuffer.mNumberBuffers = 1;
					dataBuffer.mBuffers[0].mDataByteSize = size;
					dataBuffer.mBuffers[0].mNumberChannels = environment->format.mChannelsPerFrame;
					dataBuffer.mBuffers[0].mData = buffer;

					// バッファにデータを読み込む
					UInt32 fileLengthFrames;
					ExtAudioFileRead(environment->file, &fileLengthFrames, &dataBuffer) == errSecSuccess;
				}
			}

			void rewind(void* self)
			{
				Environment* environment = reinterpret_cast<Environment*>(self);
				if(environment)
				{
				}
			}

			void close(void* self)
			{
				Environment* environment = reinterpret_cast<Environment*>(self);
				if(environment)
				{
					// オーディオファイルを破棄する
					if(environment->file)
						ExtAudioFileDispose(environment->file);
				}

				// バッファを解放
				free(self);
			}
#endif
		}
	}
}
